/**
 * @file JsonFlatMapAccessor.h
 * @brief Provides access to JSON data as a flattened map of strings.
 */

#ifndef JSON_FLAT_MAP_ACCESSOR_H
#define JSON_FLAT_MAP_ACCESSOR_H

#include <string>
#include <map>
#include <vector>
#include <mutex> // For lazy loading cache
#include "json.hpp"
#include <iostream> // Added for std::cerr
#include <sstream> // Required for path splitting
#include <functional> // Added
#include <optional> // Added for optional output parameter
#include <type_traits> // Added for potential SFINAE/concepts later if needed
#include <concepts> // Added for C++20 Concepts
#include <utility> // Added for std::pair
#include <regex> // Added for regex search

// --- Concept Definition ---
// Requires C++20
template <typename T>
concept StringMapLike = requires(T m, const std::pair<const std::string, std::string>& p) {
    // Check for existence of clear() method returning void
    { m.clear() } -> std::same_as<void>;
    // Check for existence of insert() method accepting the required pair type
    // Note: This doesn't strictly check the return type of insert, but that's usually sufficient.
    m.insert(p);
    // Add a check for value_type if stricter compatibility is needed
    // requires std::same_as<typename T::value_type, std::pair<const std::string, std::string>>;
};

/**
 * @class JsonFlatMapAccessor
 * @brief Provides access to JSON data flattened into a map<string, string> using full paths as keys, and allows setting values by path.
 * 
 * This class traverses the JSON structure and creates a map where keys are 
 * the full dot-separated paths from the root to the leaf nodes (e.g., "user.address.city") 
 * and values are their string representations. It uses lazy loading for its internal cache.
 * It also allows setting values at a specified path.
 * 
 * Use this accessor if you need a flattened string map representation or need to set values based on full paths. 
 * For type-safe and structured access/modification, prefer JSONHandler::nodeAccessor().
 */
class JsonFlatMapAccessor {
public:
    // Use std::function directly for notifier type
    using ModificationNotifier = std::function<void()>;

    /**
     * @brief Constructs a flat map accessor for a given JSON object.
     * @param json A reference to the nlohmann::json object to access and potentially modify.
     * @param dataVersionRef Const reference to the handler's data version counter.
     * @param notifier A callback function to call when a modification occurs via this accessor.
     */
    explicit JsonFlatMapAccessor(nlohmann::json& json, const uint64_t& dataVersionRef, ModificationNotifier notifier);

    // Deleted copy/move operations for simplicity and because it holds a reference
    JsonFlatMapAccessor(const JsonFlatMapAccessor&) = delete;
    JsonFlatMapAccessor& operator=(const JsonFlatMapAccessor&) = delete;
    JsonFlatMapAccessor(JsonFlatMapAccessor&&) = delete;
    JsonFlatMapAccessor& operator=(JsonFlatMapAccessor&&) = delete;

    /**
     * @brief Retrieves the value associated with a specified key (full path) as a string.
     * @param key The full path key (e.g., "mainkey.address.city") for which the value is to be retrieved.
     * @return The value as a string if found, otherwise an empty string. Populates internal cache on first call.
     * @note This uses the cached flat map.
     */
    std::string getValue(const std::string& key);

    /**
     * @brief Retrieves all non-object/non-array key-value pairs as a flattened map.
     * @param resultMap Pointer to a map where the results will be stored. If nullptr, 
     *                  the operation is effectively a no-op for external results but 
     *                  still populates the internal cache.
     * @return True if at least one key-value pair is found and added to the map 
     *         (or internal cache), false otherwise. Populates internal cache on first call.
     */
    template <typename OutputMap>
    requires StringMapLike<OutputMap>
    bool getAllValue(OutputMap* resultMap);

    /**
     * @brief Retrieves all non-object/non-array key-value pairs as a flattened map (optional output via std::optional).
     * @tparam OutputMap The type of the map to store results in (e.g., std::map<std::string, std::string>).
     * @param resultMapOpt An optional reference wrapper to the map where results will be stored.
     *                     If std::nullopt, the operation only populates the internal cache.
     * @return True if at least one key-value pair is found, false otherwise. Populates internal cache.
     * @note Requires C++17.
     */
    template <typename OutputMap>
    requires StringMapLike<OutputMap>
    bool getAllValue(std::optional<std::reference_wrapper<OutputMap>> resultMapOpt);

    /**
     * @brief Overload to handle std::nullopt directly for checking cache status without providing an output map.
     * @return True if the internal cache is populated and not empty, false otherwise.
     */
    bool getAllValue(std::nullopt_t);

    /**
     * @brief Retrieves a list of all available flattened keys (full paths).
     * @return A vector containing all the keys present in the flattened map cache.
     */
    std::vector<std::string> getKeys();

    /**
     * @brief Finds keys matching a given regular expression pattern.
     * @param regexPattern An ECMAScript-style regular expression string.
     * @return A vector containing all keys that match the provided regex pattern.
     *         Returns an empty vector if no keys match or if the regex is invalid.
     * @note Uses std::regex internally. Invalid patterns will be caught and logged to cerr.
     */
    std::vector<std::string> findKeysByRegex(const std::string& regexPattern);

    /**
     * @brief Sets a value in the JSON object at the specified path.
     * @return True if the value was successfully set, false otherwise.
     * @note Triggers the modification notifier on success. Invalidates internal cache.
     */
    template <typename T>
    bool setValueByPath(const std::string& path, const T& value);

private:
    /**
     * @brief Populates the internal string map cache if it hasn't been already. Thread-safe.
     */
    void populateCacheIfNeeded();

    /**
     * @brief Recursively explores the JSON object to populate the string map cache using full paths.
     * @param currentJson The current JSON node being explored.
     * @param currentPath The dot-separated path accumulated so far from the root.
     * @param targetMap The map to populate (keys will be full paths).
     */
    void exploreAndCache(const nlohmann::json& currentJson, const std::string& currentPath, std::map<std::string, std::string>& targetMap);
   
    /**
     * @brief Helper to split a path string by dots.
     */
    std::vector<std::string> splitPath(const std::string& path) const;

    nlohmann::json& m_jsonRef; ///< Reference to the original JSON object (mutable).
    const uint64_t& m_dataVersionRef; ///< Const reference to the handler's data version.
    ModificationNotifier m_notifier; ///< Callback to notify handler of modification.
    
    // Reordered cache-related members for clearer initialization order
    mutable std::mutex m_cacheMutex; ///< Mutex to protect lazy initialization of the cache.
    bool m_cachePopulated = false; ///< Flag indicating if the cache has been populated.
    uint64_t m_cachedDataVersion = 0; ///< Data version when the cache was last populated.
    std::map<std::string, std::string> m_jsonStringMap; ///< Internal cache for flattened string values.

    // bool& m_isDirtyRef; // Removed
}; 

// --- Template Implementation ---

template <typename T>
bool JsonFlatMapAccessor::setValueByPath(const std::string& path, const T& value) {
    std::vector<std::string> keys = splitPath(path);
    if (keys.empty()) {
        return false; 
    }

    nlohmann::json* currentNode = &m_jsonRef;
    
    // Traverse path until the second to last key
    for (size_t i = 0; i < keys.size() - 1; ++i) {
        const std::string& key = keys[i];
        if (!currentNode->is_object()) {
            // If intermediate path is null, try to create an object
            if (currentNode->is_null()) {
                 *currentNode = nlohmann::json::object();
                 if (m_notifier) m_notifier(); // Notify because we changed null to object
            } else {
                 // Cannot traverse further if not an object or null
                 return false; 
            }
        }
        currentNode = &(*currentNode)[key]; 
    }

    // Set the value at the final key
    const std::string& lastKey = keys.back();
     if (!currentNode->is_object()) {
         // If final parent node is null, create an object first
         if (currentNode->is_null()) {
             *currentNode = nlohmann::json::object();
             if (m_notifier) m_notifier(); // Notify because we changed null to object
         } else {
            // The parent must be an object to set a key/value pair
            return false;
         }
     }
     
    try {
        // Check if value actually changes before notifying
        if (!currentNode->contains(lastKey) || (*currentNode)[lastKey] != value) {
             (*currentNode)[lastKey] = value;
             if (m_notifier) {
                 m_notifier(); // Notify handler: sets dirty, increments version
             }
            // Clear the cache flag since modification occurred
            {
                std::lock_guard<std::mutex> lock(m_cacheMutex);
                m_cachePopulated = false; 
                // Clearing the map itself is optional, it will be rebuilt anyway
                // m_jsonStringMap.clear();
            }
        }
        return true;
    } catch (const nlohmann::json::exception& e) {
        std::cerr << "JsonFlatMapAccessor::setValueByPath Error setting value for path '" << path << "': " << e.what() << std::endl;
        return false;
    } catch (...) {
         std::cerr << "JsonFlatMapAccessor::setValueByPath Unknown error setting value for path '" << path << "'." << std::endl;
         return false;
    }
}

inline std::vector<std::string> JsonFlatMapAccessor::splitPath(const std::string& path) const {
    std::vector<std::string> result;
    std::stringstream ss(path);
    std::string segment;
    while (std::getline(ss, segment, '.')) {
        if (!segment.empty()) { // Avoid empty segments if there are consecutive dots
            result.push_back(segment);
        }
    }
    return result;
}

// --- Template Implementation for getAllValue (Pointer version) ---
template <typename OutputMap>
requires StringMapLike<OutputMap>
bool JsonFlatMapAccessor::getAllValue(OutputMap* resultMap) {
    // If resultMap is null, simply return false. Don't check cache status.
    if (!resultMap) {
        return false; 
    }

    populateCacheIfNeeded(); // Ensure the cache is populated
    
    // Original logic for non-null resultMap remains
    std::lock_guard<std::mutex> lock(m_cacheMutex); // Lock for reading the cache
    resultMap->clear(); // Clear the provided map before filling
    // Insert elements from the internal cache into the provided map
    // This assumes OutputMap supports insert or emplace for std::pair<const std::string, std::string>
    // or compatible types.
    for (const auto& pair : m_jsonStringMap) {
        resultMap->insert(pair); // Or use emplace if preferred and applicable
    }
    return !resultMap->empty();
}

// --- Template Implementation for getAllValue (std::optional version) ---
template <typename OutputMap>
requires StringMapLike<OutputMap>
bool JsonFlatMapAccessor::getAllValue(std::optional<std::reference_wrapper<OutputMap>> resultMapOpt) {
    populateCacheIfNeeded(); // Ensure the cache is populated

    if (resultMapOpt.has_value()) {
        OutputMap& mapRef = resultMapOpt.value().get(); // Get the actual map reference
        std::lock_guard<std::mutex> lock(m_cacheMutex); // Lock for reading the cache
        mapRef.clear(); // Clear the provided map before filling
        // Insert elements from the internal cache into the provided map
        for (const auto& pair : m_jsonStringMap) {
            mapRef.insert(pair); // Or use emplace if preferred and applicable
        }
        return !mapRef.empty();
    }
    // If resultMapOpt is nullopt, cache is still populated, return based on cache state
    std::lock_guard<std::mutex> lock(m_cacheMutex); // Need lock to check internal map safely
    return m_cachePopulated && !m_jsonStringMap.empty();
}

// --- Implementation for getAllValue (std::nullopt_t version) ---
inline bool JsonFlatMapAccessor::getAllValue(std::nullopt_t) {
    // This version only cares about checking the cache status.
    populateCacheIfNeeded(); // Ensure the cache is populated
    std::lock_guard<std::mutex> lock(m_cacheMutex); // Lock for reading the cache state
    // Return true only if cache is populated AND the internal map is not empty
    return m_cachePopulated && !m_jsonStringMap.empty();
}

#endif // JSON_FLAT_MAP_ACCESSOR_H
